#include "md5_lexer.h"
#include "string_util.h"

#include <cctype>
#include <fstream>
#include <iostream>
#include <iterator>

using namespace krryn::data;
using namespace krryn::core;

md5_lexer::md5_lexer(const std::string &a_Filename) 
	: m_LineNumber(1)
{
	std::ifstream l_File(a_Filename.c_str());

    if(l_File){
		l_File.seekg(0, std::ios::end);
		std::ifstream::pos_type l_Length = l_File.tellg();
        l_File.seekg(0, std::ios::beg);
		m_Buffer.resize(l_Length + (std::ifstream::pos_type)1); // to avoid check in read_lexeme
        l_File.read(&m_Buffer[0], l_Length);
		m_Buffer[l_Length] = 0;
		m_BufferPos = 0;
	}else{
		throw std::runtime_error("Can't find or read file " + a_Filename);
	}

	// load reserved words
	KEYWORDS(KEYWORDS_VALUES);
}

unsigned int md5_lexer::line(){
	return m_LineNumber;
}

md5_token md5_lexer::scan(){
	if(m_BufferPos >= m_Buffer.size() - 1){
		throw md5_lexer_eof();
	}

	owned_string l_Lexeme = read_lexeme();

	if(l_Lexeme.empty()) return scan();
	else return tokenize(l_Lexeme);
}

owned_string md5_lexer::read_lexeme(){
	owned_string l_Lexeme(&m_Buffer[0] + m_BufferPos, 0);
	bool l_Quoted = false;

	while(m_BufferPos < m_Buffer.size() - 1){
		char l_Char = m_Buffer[m_BufferPos++];
		char l_Peek = m_Buffer[m_BufferPos];

		// skip whitespace and comments unless we're in a string,
		// keep track of linenumber for debugging / error checking
		if(l_Char == '"' || l_Quoted){
			l_Lexeme.m_Length++;

			// end of string?
			if(l_Char == '"' && l_Quoted) break;
			else l_Quoted = true;
		}else if(l_Char == ' ' || l_Char == '\t'){
			break;
		}else if(l_Char == '\n'){
			m_LineNumber++;
			break;
		}else if(l_Char == '/' && l_Peek == '/'){
			while(m_Buffer[m_BufferPos] != '\n' && m_BufferPos < m_Buffer.size() - 1) 
				m_BufferPos++;

			m_LineNumber++;
			break;
		}else{
			l_Lexeme.m_Length++;
			if(l_Peek == '"') break;
		}
	}

	return l_Lexeme;
}

md5_token md5_lexer::tokenize(owned_string a_String){
	owned_string l_Lexeme = a_String.trim();

		 if(l_Lexeme == "{")      return make(open_brace,  l_Lexeme);
	else if(l_Lexeme == "}")      return make(close_brace, l_Lexeme);
	else if(l_Lexeme == "(")      return make(open_set,    l_Lexeme);
	else if(l_Lexeme == ")")      return make(close_set,   l_Lexeme);
	else if(is_integer(l_Lexeme)) return make(integer,     l_Lexeme);
	else if(is_real(l_Lexeme))    return make(real,        l_Lexeme);
	else if(is_keyword(l_Lexeme)) return make_keyword(     l_Lexeme);
	else                          return make(string,      l_Lexeme);
}

md5_token md5_lexer::make(md5_tags a_Tag, owned_string a_Value){
	return md5_token(a_Tag, a_Value);
}

md5_token md5_lexer::make_keyword(owned_string a_Value){
	if(false) {}
	KEYWORDS(KEYWORDS_DETERMINE)
	throw md5_lexer_exception();
}

bool md5_lexer::is_real(owned_string a_String){
	std::string::size_type l_Sign = 0;
	if(has_sign(a_String)) l_Sign = 1;

	owned_string::const_iterator i = a_String.begin() + l_Sign;

	int l_DotCount = 0;
	for(; i != a_String.end(); ++i){
		if(!isdigit(*i)){
			if(*i == '.' && l_DotCount == 0 && isdigit(*++i)) l_DotCount++;
			else return false;
		}
	}

	return true;
}

bool md5_lexer::is_integer(owned_string a_String){
	owned_string::size_type l_Sign = 0;
	if(has_sign(a_String)) l_Sign = 1;

	return 
		a_String.length() - l_Sign == std::count_if(
			a_String.begin() + l_Sign, 
			a_String.end(), 
			isdigit
		);
}

bool md5_lexer::is_keyword(owned_string a_String){
	return 
		m_Keywords.end() != std::find(
			m_Keywords.begin(), 
			m_Keywords.end(), 
			a_String
		);
}

bool md5_lexer::has_sign(owned_string a_String){
	return a_String[0] == '-' || a_String[0] == '+';
}
